# -*- coding: utf8 -*-

import re
import urllib.request
from purest.web import status

_path_param_pattern = \
    re.compile(r"\{\s*(?P<name>[^:}\s]+)(\s*:\s*(?P<pattern>[^\s]+)\s*)?\}")

class Router(object):
    """
    Router is a WSGI middleware dispatching requests to WSGI applications.

    The router takes the URL decoded 'PATH_INFO' from the WSGI environment, 
    matches it against the path patterns of connected WSGI applications and 
    forwards the request to the matched application. The path pattern can 
    contain variables. The values of those variables are added to the WSGI 
    environment as 'wsgiorg.routing_args' (see: 
    http://wsgi.org/wsgi/Specifications/routing_args).

    Paths are matched in the same order in which the Routes were passed to the 
    constructor. Paths get URL decoded before matching.

    Example:

    router = Router(
        Route(r"/static/path/", wsgi_app1),
        Route(r"/test/{id: \d+}", wsgi_app2),
        Route(r"/location/{city}/{street}", wsgi_app3),
        Route(r"/path with spaces/", wsgi_app4)
    )
    """

    def __init__(self, *routes):
        """ Constructs a router from a list of one or more 'Route' objects. 
        
        Each target can only be registered with one route.
        """
        self._routes = routes
        self._routes_by_target = {}
        for route in routes:
            assert route.target not in self._routes_by_target, \
                "A route with the same target was already registered." 
            self._routes_by_target[route.target] = route

    def get_route_to(self, target):
        """ Returns the route with the given target. """
        return self._routes_by_target[target]

    def __call__(self, environ, start_response):
        """ Handles the HTTP request as WSGI middleware. """
        request_path = urllib.request.url2pathname(environ["PATH_INFO"])
        for route in self._routes:
            matcher = route.match(request_path)
            if matcher:
                positional_params, named_params = \
                    environ.get('wsgiorg.routing_args', ((), {}))
                positional_params = \
                    list(positional_params) + list(matcher.groups())
                named_params = named_params.copy()
                named_params.update(matcher.groupdict())
                environ['wsgiorg.routing_args'] = positional_params,named_params
                return route.target(environ, start_response)
        return self._not_found(environ, start_response)

    def _not_found(self, environ, start_response):
        start_response(status.NOT_FOUND, [('Content-Type', 'text/plain')])
        return [status.NOT_FOUND.encode(encoding='utf_8', errors='strict')]

    
class Route(object):
    """Connects a path like '/items/{id}' with a callable WSGI application."""

    __slots__ = ("_pattern", "_template", "target")

    def __init__(self, path, target):
        """
        The path can contain parameters like '{id}. Allowed values of each 
        parameter can be limited by a regex. The syntax is: '{NAME: REGEX}'. 
        Example: '{id: \\d{1,3}}' If no regex is given, the default regex
        '[^/]+' will be used.

        Arguments:
        - path: A pattern that starts with '/' and that can contain variables in
        curly braces.
        - target: A WSGI callable.
        """
        
        def create_regex(path):
            """ 
            Transforms parameters to named capturing groups and compiles regex.
    
            Returns a compiled regex.
            """
            def replace_path_params_with_capturing_groups(matcher):
                group_name = "(?P<" + matcher.group("name") + ">"
                if matcher.group("pattern") is None:
                    group_pattern = "[^/]+"
                else:
                    group_pattern = matcher.group("pattern")
                return group_name + group_pattern + ")"
            regex = _path_param_pattern.sub(
                        replace_path_params_with_capturing_groups, path) + "$"
            return re.compile(regex)
        
        def create_template(path):
            # "{params[GROUPNAME]}" is used for string formatting
            return _path_param_pattern.sub("{params[\g<name>]}", path)
        
        self._pattern = create_regex(path)
        self._template = create_template(path)
        self.target = target

    def match(self, request_path):
        """ Matches the given path against the internal path pattern. 
        
        Returns a re.MatchObject or None, if not matching.
        """
        return self._pattern.match(request_path)

    def render(self, **path_params):
        """ Renders a path to the Route's target with the given path_params. 
        
        Arguments:
        - path_params: A list of named parameters, where the names reflect the
        names of the path parameters.
        """
        return self._template.format(params=path_params)

